package domein;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.DefaultListModel;
import javax.swing.JMenuItem;
import javax.swing.UIManager;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;

import persistence.PersistenceController;

import domain.DomainController;
import domain.cooldown.CoolDownQueue;
import domain.enums.Protection;
import domain.enums.View;
import domain.error.Error;
import domain.error.ErrorCollection;
import domain.forumdata.Categories;
import domain.forumdata.Category;
import domain.forumdata.Forum;
import domain.forumdata.ForumCategory;
import domain.forumdata.Forums;
import domain.forumdata.Post;
import domain.forumdata.Queue;
import domain.forumdata.tag.Tag;
import domain.forumdata.tag.Tags;
import domain.history.History;
import domain.settings.Preferences;
import domain.settings.Settings;
import domain.template.Template;
import domain.template.Templates;
import domain.threading.BatchPosterStarter;
import domain.threading.ForumTester;
import domain.threading.MovieInfo;
import domain.threading.MoviePoster;
import domain.threading.PosterStarter;
import domain.threading.UpdateChecker;
import domain.threading.UploadImage;
import domain.ui.AddEditForumUI;
import domain.ui.LoadingUI;
import domain.ui.LookAndFeel;
import domain.ui.TemplateUI;
import domain.ui.UI;
import domain.xml.ForumExporter;
import domain.xml.ForumImporter;

public class domeinControleren extends javax.swing.JPanel{
    private static domeinControleren domainController;
    
    private static final Logger logger = Logger.getLogger(Settings.APP_NAME);

    private LoadingUI loadingUI;
    private UI ui;
    private AddEditForumUI addEditForumUI;
    private TemplateUI templateUI;

    private JMenuItem undoItem;
    private JMenuItem redoItem;
    private UndoManager undoManager;

    private Categories categories;
    private Forums forums;
    private History history;
    private Templates templates;
    private LookAndFeel lookAndFeel;
    private Queue queue;
    private Preferences preferences;

    private ArrayList<ForumCategory> tempForumCategories;
    private ArrayList<String> lastPostTopicURLResults;
    private File lastSelectedFolder;
    private CoolDownQueue coolDownQueue;
    private ErrorCollection errorCollection;

    private boolean locked;

    /**
     * @return The one and only instance of this class.
     */
    public static domeinControleren getInstance()
    {
        if(domainController == null)
            domainController = new domeinControleren();
        return domainController;
    }

    /**
     * Default constructor that initializes the variables.
     */
    public domeinControleren()
    {
        this.lastPostTopicURLResults = new ArrayList<String>();
        this.undoManager = new UndoManager();
        this.undoItem = null;
        this.redoItem = null;
        this.lastSelectedFolder = new File(Settings.HOME_DIR);
        this.coolDownQueue = new CoolDownQueue();
        this.errorCollection = new ErrorCollection();
    }

    /**
     * Loads the categories, forums, history and templates and notifies the UI.
     * This method also verifies if the app folder exists.
     */
    public void load()
    {
        int adder = 100 / 8; //used for progress...
        int progress = 0;

        this.updateLoadingUI(progress, "Evaluating file system...");
        PersistenceController.checkDir();

        this.updateLoadingUI(progress += adder, "Loading categories...");
        this.categories = PersistenceController.loadCategories();

        this.updateLoadingUI(progress += adder, "Loading forums...");
        this.forums = PersistenceController.loadForums();

        this.updateLoadingUI(progress += adder, "Loading history...");
        this.history = PersistenceController.loadHistory();

        this.updateLoadingUI(progress += adder, "Loading templates...");
        this.templates = PersistenceController.loadTemplates();

        this.updateLoadingUI(progress += adder, "Loading Look & Feel...");
        this.lookAndFeel = PersistenceController.loadLookAndFeel();

        this.updateLoadingUI(progress += adder, "Loading queue...");
        this.queue = PersistenceController.loadQueue();

        this.updateLoadingUI(progress += adder, "Loading preferences...");
        this.preferences = PersistenceController.loadPreferences();

        this.locked = this.history.getProtection() != null;

        //Finally...
        this.loadingUI.setToLoadUI();
        this.loadLookAndFeelUI();
        this.loadCategoriesUI();
        this.loadForumsUI();
        this.loadHistoryUI();
        this.loadMenuBarUI();
        this.loadPasswordUI();
        this.loadQueueUI();
        this.loadThreadUI();
        this.loadCoolDownUI();
        this.loadRetryUI();
        
        this.loadingUI.complete();
    }

    /**
     * Notifies the UI to update its information.
     * @param progress The progress.
     * @param text The text to show to the user.
     */
    private void updateLoadingUI(int progress, String text)
    {
        this.loadingUI.updateUI(progress, text);
        try
        {
            Thread.sleep(250);
        }catch(Exception e){/*ignore*/}
    }

    /**
     * Notifies the UI to (re)load the retry UI.
     */
    public void loadRetryUI()
    {
        this.ui.loadRetry();
    }

    /**
     * Notifies the UI to (re)load the cool down period UI.
     */
    public void loadCoolDownUI()
    {
        this.ui.loadCoolDownPeriod();
    }

    /**
     * Notifies the UI to (re)load the threads UI.
     */
    public void loadThreadUI()
    {
        this.ui.loadThreads();
    }

    /**
     * Notifies the UI to (re)load the queue.
     */
    public void loadQueueUI()
    {
        this.ui.loadQueue();
    }

    /**
     * Notifies the UI to (re)load the password UI.
     */
    public void loadPasswordUI()
    {
        this.ui.updatePasswordUI();
    }

    /**
     * Notifies the UI to (re)load its look and feel.
     */
    public void loadLookAndFeelUI()
    {
        this.ui.loadLookAndFeel();
    }

    /**
     * Notifies the UI to (re)load its menu bar.
     */
    public void loadMenuBarUI()
    {
        this.ui.loadMenuBar();
    }

    /**
     * Notifies the UI to (re)load the history.
     */
    public void loadHistoryUI()
    {
        this.ui.loadHistory(this.history.getHistory());
    }

    /**
     * Notifies the UI to (re)load the forums.
     */
    public void loadForumsUI()
    {
        this.ui.loadForums();
        this.ui.updateForumUI();
    }

    /**
     * Notifies the UI to (re)load the categories.
     */
    public void loadCategoriesUI()
    {
        this.ui.loadCategories();
    }

    /**
     * @return The logger of this app.
     */
    public Logger getLogger()
    {
        return logger;
    }

    /**
     * Sets the UI to use.
     * @param ui The class that implements the UI interface.
     * @see UI
     */
    public void addObserver(UI ui)
    {
        this.ui = ui;
    }

    /**
     * Sets the UI to use when adding or editing a forum.
     * @param aui The class that implements AddEditForumUI interface.
     * @see AddEditForumUI
     */
    public void setAddEditForumUI(AddEditForumUI aui)
    {
        this.addEditForumUI = aui;
    }

    /**
     * Sets the UI to use when managing the templates.
     * @param tui The class that implements the TemplateUI interface.
     * @see TemplateUI
     */
    public void setTemplateUI(TemplateUI tui)
    {
        this.templateUI = tui;
    }

    /**
     * Saves the log to a file.
     * @param saveTo The full path to save to.
     */
    public void saveLogAs(File saveTo)
    {
        if(saveTo != null)
            PersistenceController.saveText(saveTo, this.ui.getLog());
    }

    /**
     * Calling this method will save the categories.
     */
    public void saveCategories()
    {
        PersistenceController.saveCategories(this.categories);
    }

    /**
     * Calling this method will save the forums.
     */
    public void saveForums()
    {
        PersistenceController.saveForums(this.forums);
    }

    /**
     * @return the available categories.
     */
    public Categories getCategories()
    {
        return categories;
    }

    /**
     * Shows a message at the registered UI.
     * @param message The message to show.
     * @param title The title of the message.
     */
    public void fireMessage(String message, String title)
    {
        this.ui.fireInformationMessage(message, title);
    }

    /**
     * Removes the category that is selected at the registered UI.
     */
    public void removeSelectedCategory()
    {
        String selected = this.ui.getSelectedCategory();
        if(!selected.equals(Settings.NULL_VALUE))
        {
            //Check if the category has been allocated.
            boolean canRemove = true;
            String forumsInUse = "";
            
            for(Forum f : this.forums.getForums())
            {
                for(ForumCategory fc : f.getCategories())
                {
                    if(fc.getCategory().getName().equals(selected))
                    {
                        canRemove = false;
                        forumsInUse += f.getForumName() + ", ";
                        break;
                    }
                }
            }

            if(canRemove)
            {
                this.categories.removeCategory(selected);
            }
            else
            {
                String errMessage = "Can't remove category: the selected category is in use by " + forumsInUse.substring(0, forumsInUse.length() - 2) + ".";
                this.getLogger().log(Level.WARNING, errMessage);
                this.fireMessage(errMessage, "Can't remove category");
            }
        }
        else
        {
            String errMessage = "No category selected, please try again.";
            this.getLogger().log(Level.WARNING, errMessage);
            this.fireMessage(errMessage, "Error");
        }
    }

    /**
     * @return the available forums.
     */
    public Forums getForums()
    {
        return forums;
    }

    /**
     * Initiates an empty list of forum categories.
     * @see ForumCategory
     */
    public void initTempForumCategories()
    {
        this.tempForumCategories = new ArrayList<ForumCategory>();
    }

    /**
     * Adds a new temporary forum category.
     * @param category The category for the specific forum.
     * @param url The url of that forum category.
     */
    public void addTempForumCategory(String category, String url)
    {
        this.tempForumCategories.add(new ForumCategory(this.categories.getCategoryByName(category), url));
        this.addEditForumUI.categoryAddedOrRemoved();
    }

    /**
     * Removes a temporary forum category.
     * @param name The category for the specific forum.
     */
    public void removeTempForumCategory(String name)
    {
        ForumCategory forumCat = null;
        
        for(ForumCategory fc : this.tempForumCategories)
        {
            if(fc.getCategory().getName().equals(name))
            {
                forumCat = fc;
                break;
            }
        }

        if(forumCat != null)
        {
            this.tempForumCategories.remove(forumCat);
        this.addEditForumUI.categoryAddedOrRemoved();
        }
        else
            this.getLogger().log(Level.WARNING, "Cannot remove forum category (not found).");
    }

    /**
     * @return the temporary forum categories.
     */
    public ArrayList<ForumCategory> getTempForumCategories()
    {
        return tempForumCategories;
    }

    /**
     * @return A list of categories that are not yet assigned to the forum.
     */
    public ArrayList<Category> getNotAssignedCategories()
    {
        ArrayList<Category> cats = new ArrayList<Category>();
        
        for(Category cat : this.getCategories().getCategories())
        {
            Boolean add = true;
            for(ForumCategory fc : this.tempForumCategories)
            {
                if(fc.getCategory().getName().equals(cat.getName()))
                {
                    add = false;
                }
            }
            if(add)
            {
                cats.add(cat);
            }
        }

        return cats;
    }

    /**
     * @return all forums that are selected at the UI.
     */
    public String[] getSelectedForums()
    {
        return this.ui.getSelectedForums();
    }

    /**
     * Sets the temporary forum categories. Used when editing a forum.
     * @param tempForumCategories A list of the temporary forum categories.
     */
    public void setTempForumCategories(ArrayList<ForumCategory> tempForumCategories)
    {
        this.tempForumCategories = tempForumCategories;
    }

    /**
     * Notifies the UI that the user wants to start posting.
     */
    public void postNewTopic()
    {
        this.ui.postNewTopic();
    }

    /**
     * Notifies the UI to give a preview of the new topic.
     */
    public void previewPost()
    {
        this.ui.previewNewTopic();
    }

    /**
     * Updates the progress at the UI.
     * @param progress The percentage.
     * @param status A string that explains the status.
     */
    public void updateProgress(int progress, String status)
    {
        this.ui.updateProcess(progress, status);
    }

    /**
     * When posting to a forum is completed and successful,
     * this app will try to extract the new topic URL.
     * This topic URL will be added to a list so the user
     * can visit the link when posting is done.
     * @return A list of URLs.
     */
    public ArrayList<String> getLastPostTopicURLResults()
    {
        return lastPostTopicURLResults;
    }

    /**
     * Resets (clears) the topic urls.
     */
    public void resetLastPostTopicURLResults()
    {
        this.lastPostTopicURLResults = new ArrayList<String>();
    }

    /**
     * @return The current registered UI.
     */
    public UI getUi()
    {
        return ui;
    }

    /**
     * Starts posting to the selected forums.
     * @param category The category to post to.
     * @param topicTitle The title of the new topic.
     * @param topicSubtitle The subtitle or a small description of the new topic.
     * @param tag The tag to use.
     * @param post The message of the new topic.
     * @param selectedForums The forums to post to.
     */
    public void startPosting(String category, String topicTitle, String topicSubtitle, String tag, String post, String[] selectedForums)
    {
        this.ui.setButtonsEnabled(false);
        this.updateProgress(0, "Preparing...");
        this.getLogger().log(Level.INFO, "Preparing for posting...");

        //Selected forums...
        ArrayList<Forum> theSelectedForums = new ArrayList<Forum>();
        for(String s : selectedForums)
        {
            theSelectedForums.add(this.forums.getForumByName(s));
        }

        //Category and tag
        Category cat = this.categories.getCategoryByName(category);
        Tag theTag = null;
        for(Tag t : Tags.getTags())
        {
            if(t.getTagName().equals(tag))
            {
                theTag = t;
                break;
            }
        }

        boolean ok = true;

        for(Forum f : theSelectedForums)
        {
            if(!f.hasCategory(cat))
            {
                ok = false;
                break;
            }
        }

        if(!ok)
            ok = this.ui.fireYesNoQuestion("Not all of the selected forums have the category \"" + category + "\" assigned."
                                            + Settings.LINE_SEPARATOR
                                            + "Do you want to continue?", "Attention needed");

        if(ok)
        {
            final PosterStarter ps = new PosterStarter(cat, topicTitle, topicSubtitle, theTag, post, theSelectedForums, false);

            ExecutorService exec = Executors.newFixedThreadPool(1);

            this.getUi().updateProcess(0, "Posting...");
            
            exec.execute(new Runnable()
            {
                public void run()
                {
                    ps.post();
                }
            });
            
            exec.shutdown();
        }
        else
        {
            this.getLogger().log(Level.INFO, "Posting aborted by user.");
            this.ui.setButtonsEnabled(true);
            this.updateProgress(100, "Aborted");
            this.ui.fireDone();
        }
    }

    /**
     * Notifies the UI to reset the post form.
     */
    public void clearPost()
    {
        this.ui.clearPost();
        this.getLogger().log(Level.INFO, "Post cleared.");
    }

    /**
     * @return The history of successful posts.
     */
    public History getHistory()
    {
        return history;
    }

    /**
     * Saves the history.
     */
    public void saveHistory()
    {
        PersistenceController.saveHistory(this.history);
    }

    /**
     * Loads a specified history item and notifies the UI to show it.
     * @param title The title of the history entry.
     */
    public void loadHistoryItem(String title)
    {
        Post post = null;
        
        for(Post p : this.getHistory().getHistory())
        {
            if(p.getDisplayTitle().equals(title))
            {
                //Match!
                post = p;
                break;
            }
        }

        if(post != null)
            this.ui.loadHistoryItem(post);
    }

    /**
     * Notifies the UI to load a preview post.
     * @param post The object that contains all information about a post.
     */
    public void loadPostInUI(Post post)
    {
        if(post != null)
            this.ui.loadPost(post);
    }

    /**
     * Removes a history entry from the history list.
     * @param title The title of the history entry.
     */
    public void removeHistoryItem(String title)
    {
        Post p = null;

        for(Post post : this.getHistory().getHistory())
        {
            if(post.getDisplayTitle().equals(title))
            {
                p = post;
                break;
            }
        }

        if(p != null)
        {
            this.getHistory().getHistory().remove(p);
            this.getLogger().log(Level.INFO, "History entry \"{0}\" removed.", title);
            this.saveHistory();
            this.loadHistoryUI();
        }
    }

    /**
     * Notifies the UI to switch to a certain view.
     * @param view The view to switch to.
     */
    public void changeViewTo(View view)
    {
        this.ui.changeViewTo(view);
    }

    /**
     * @return all available templates.
     */
    public Templates getTemplates()
    {
        return templates;
    }

    /**
     * Loads the specified template and notifies the UI to use it as a new post.
     * @param template The template to load.
     */
    public void loadTemplate(Template template)
    {
        if(template != null)
        {
            this.ui.setPostBodyTo(template.getBody());
            this.getLogger().log(Level.INFO, "Template \"{0}\" loaded into post.", template.getName());
        }
    }

    /**
     * Loads the specified template and notifies the UI to show it.
     * @param name The name of the template.
     */
    public void previewTemplate(String name)
    {
        this.templateUI.loadTemplate(this.templates.getTemplateByName(name));
    }

    /**
     * Creates a new empty template and notifies the UI to reload.
     * @param name The name of the new template.
     */
    public void addNewTemplate(String name)
    {
        this.templates.getTemplates().add(new Template(name, ""));
        this.getLogger().log(Level.INFO, "Template \"{0}\" has been created.", name);
        this.templateUI.loadList();
        this.templateUI.setSelectedTemplate(name);
        this.ui.loadMenuBar();
        PersistenceController.saveTemplates(this.templates);
    }

    /**
     * Removes the template that is selected at the UI.
     */
    public void removeSelectedTemplate()
    {
        String selected = this.templateUI.getSelectedTemplate();
        if(selected != null)
        {
            this.templates.remove(selected);
            this.getLogger().log(Level.INFO, "Template \"{0}\" has been removed.", selected);
            this.templateUI.loadList();
            this.ui.loadMenuBar();
            PersistenceController.saveTemplates(this.templates);
        }
    }

    /**
     * Saves the selected template.
     */
    public void saveTemplate()
    {
        String name = this.templateUI.getSelectedTemplate();
        String body = this.templateUI.getBody();

        this.templates.getTemplateByName(name).setBody(body);
        PersistenceController.saveTemplates(this.templates);
    }

    /**
     * Saves the post body to a file.
     * @param file The full path to save to.
     */
    public void savePostTo(File file)
    {
        if(file != null)
            PersistenceController.saveText(file, this.ui.getPost());
    }

    /**
     * Loads a file and notifies the UI to set the post body to the content of 
     * the file.
     * @param file The file to open.
     */
    public void loadPost(File file)
    {
        if(file != null)
            this.ui.setPostBodyTo(PersistenceController.getText(file));
    }

    /**
     * Notifies the UI to insert a tag at the caret position of the post body.
     * @param tag
     */
    public void insertTagInPostBody(String tag)
    {
        this.ui.insertTagInPostBody(tag);
    }

    /**
     * Registers the undo button.
     * @param undo The undo button.
     */
    public void setUndo(JMenuItem undo)
    {
        this.undoItem = undo;
    }

    /**
     * Registers the redo button.
     * @param redo The redo button.
     */
    public void setRedo(JMenuItem redo)
    {
        this.redoItem = redo;
    }

    /**
     * Undoes the appropriate edits.
     */
    public void undo()
    {
        try
        {
            this.undoManager.undo();
        }
        catch(CannotUndoException cue)
        {
            this.getLogger().log(Level.WARNING, "Cannot undo ({0})", cue.getMessage());
        }
        this.updateUndoRedoUI();
    }

    /**
     * Redoes the appropriate edits.
     */
    public void redo()
    {
        try
        {
            this.undoManager.redo();
        }
        catch(CannotRedoException cre)
        {
            this.getLogger().log(Level.WARNING, "Cannot redo ({0})", cre.getMessage());
        }
        this.updateUndoRedoUI();
    }

    /**
     * @return The undo manager.
     */
    public UndoManager getUndoManager()
    {
        return undoManager;
    }

    /**
     * Verifies whether the undo/redo buttons should be enabled or not.
     */
    public void updateUndoRedoUI()
    {
        if(this.undoItem != null && this.redoItem != null)
        {
            //this.undoItem.setText(this.undoManager.getUndoPresentationName());
            //this.redoItem.setText(this.undoManager.getRedoPresentationName());
            this.undoItem.setEnabled(this.undoManager.canUndo());
            this.redoItem.setEnabled(this.undoManager.canRedo());
        }
    }

    /**
     * Return a number representing the count of a specific forum.
     * @param forumType the forum type.
     * @return the count.
     */
    public int getNumberOfForumsOfType(String forumType)
    {
        int counter = 0;
        
        for(Forum f : this.getForums().getForums())
        {
            if(f.getForumType().equals(forumType))
                counter++;
        }

        return counter;
    }

    /**
     * Enables the forum GUI if disabled and vice versa.
     */
    public void toggleForumPanel()
    {
        this.forums.setViewUI(!this.forums.isViewUI());
        this.saveForums();
        this.ui.updateForumUI();
    }

    /**
     * Downloads the info of the given movie title and notifies the UI.
     * @param movieName The title of the movie.
     */
    public void getMovieInfo(String movieName)
    {
        this.ui.setButtonsEnabled(false);
        this.ui.setStatusIndeterminate(true, "Fetching...");
        MovieInfo mi = new MovieInfo(movieName);
        mi.start();
    }

    /**
     * Asks the user for a CAPTCHA code.
     * @param src The image URL of the CAPTCHA code.
     * @return the user input.
     */
    public String askCaptchaCode(String src)
    {
        return this.ui.askCaptchaCode(src);
    }

    /**
     * Uploads an image and notifies the UI.
     * @param selectedFile The file to upload.
     */
    public void uploadImage(File selectedFile)
    {
        this.ui.setButtonsEnabled(false);
        this.ui.setStatusIndeterminate(true, "Uploading...");
        UploadImage uimg = new UploadImage(selectedFile);
        uimg.start();
    }

    /**
     * Tests a certain forum.
     * @param forum The forum object to test.
     */
    public void testForum(Forum forum)
    {
        this.ui.setButtonsEnabled(false);
        this.ui.setStatusIndeterminate(true, "Testing...");
        ForumTester forumTester = new ForumTester(forum);
        forumTester.start();
    }

    /**
     * Notifies the UI with the result.
     * @param result True if succesful, false otherwise.
     */
    public void forumTestCompleted(boolean result)
    {
        this.ui.setButtonsEnabled(true);
        this.ui.setStatusIndeterminate(false, "Done");
        this.addEditForumUI.testCompleted(result);
    }

    /**
     * Fetches a movie poster and uploads it.
     * @param movieName The movie title.
     */
    public void getAndInsertMoviePoster(String movieName)
    {
        MoviePoster moviePoster = new MoviePoster(movieName);
        moviePoster.start();
    }

    /**
     * @return The selected text in the post body.
     */
    public String getSelectedTextInPost()
    {
        return this.ui.getSelectedTextInPost();
    }

    /**
     * @return The folder that was previously selected by the user.
     */
    public File getLastSelectedFolder()
    {
        return lastSelectedFolder;
    }

    /**
     * Sets the folder as last selected.
     * @param lastSelectedFolder The file object.
     */
    public void setLastSelectedFolder(File lastSelectedFolder)
    {
        this.lastSelectedFolder = lastSelectedFolder;
    }

    /**
     * @return All available Look And Feels for this program.
     */
    public String[] getLookAndFeels()
    {
        return this.lookAndFeel.getLookAndFeels();
    }

    /**
     * @return The look and feel object.
     */
    public LookAndFeel getLookAndFeel()
    {
        return lookAndFeel;
    }

    /**
    * Sets the working LAF.
    * @param selected The LAF to select.
    */
    public void setLookAndFeel(String selected)
    {
        this.lookAndFeel.setSelectedLookAndFeel(selected);

        //Only save when changed.
        if(!UIManager.getLookAndFeel().getName().equals(selected))
            this.saveLookAndFeel();

        String className = this.lookAndFeel.getClassNameFromName(selected);
        this.ui.setLookAndFeel(className);
    }

    /**
     * Saves the look and feels to disk.
     */
    public void saveLookAndFeel()
    {
        PersistenceController.saveLookAndFeel(this.lookAndFeel);
    }

    /**
     * Stores the current post into the history list.
     * If the post already exists, then the date of the existing item will be updated.
     */
    public void storePost()
    {
        if(this.isCurrentPostGoodToPost())
        {
            Post pNew = this.ui.getCurrentPost();

            //Check if there's a post with the exact same properties; except for the date and topic URLs.
            Post pOld = this.getHistory().getExactPost(pNew);

            String result = "";

            if(pOld == null)
            {
                //This post is unique, just add it.
                this.getHistory().getHistory().add(pNew);
                result = "Post stored as \"" + pNew.getDisplayTitle() + "\"!";
            }
            else
            {
                //This post has already been stored, so just update the date.
                pOld.updatePost();
                result = "Post already stored, but updated to \"" + pOld.getDisplayTitle() + "\"!";
            }

            this.saveHistory();
            this.loadHistoryUI();

            this.getLogger().log(Level.INFO, result);
            this.ui.fireInformationMessage(result, "Store post");
        }
    }

    /**
     * Searches the entries of the history for the entered keyword(s).
     * @param text The text to search.
     */
    public void doSearchHistory(String text)
    {
        if(text == null || text.equals(""))
        {
            //No search.
            this.loadHistoryUI();
        }
        else
        {
            //Search.
            this.ui.loadHistory(this.history.getHistoryWithSearch(text));
        }
    }

    /**
     * Sets the protection for the history items.
     * @param protection The protection method. (Lock or unlock)
     */
    public void setProtection(Protection protection)
    {
        if(protection == Protection.LOCK)
        {
            char[] password1 = this.ui.askPassword("Please enter a new password:", "Protection");
            char[] password2 = this.ui.askPassword("Please enter the password again:", "Protection");
            
            if(new String(password1).equals(new String(password2)))
            {
                //OK
                this.history.setProtection(password1);
                this.saveHistory();
                this.locked = true;
                this.loadHistoryUI();
                this.loadPasswordUI();
                String info = "Password set! To view your history, click 'Tools -> Unlock history'." + Settings.LINE_SEPARATOR + "To completely remove the protection, click 'Unlock' at the settings tab.";
                this.getLogger().log(Level.INFO, info);
                this.ui.fireInformationMessage(info, "Protection");
            }
            else
            {
                this.getLogger().log(Level.INFO, "The passwords don't match!");
                this.ui.fireInformationMessage("The passwords don't match!", "Protection");
            }
        }
        else
        {
            char[] password1 = this.ui.askPassword("Please enter your password:", "Protection");
            if(new String(password1).equals(new String(this.history.getProtection())))
            {
                this.locked = false;
                this.history.setProtection(null);
                this.saveHistory();
                this.loadHistoryUI();
                this.loadPasswordUI();
                String info = "The protection has been removed!";
                this.getLogger().log(Level.INFO, info);
                this.ui.fireInformationMessage(info, "Protection");
            }
            else
            {
                this.getLogger().log(Level.WARNING, "The password is incorrect!");
                this.ui.fireInformationMessage("The password is incorrect!", "Unlock");
            }
        }
    }

    /**
     * Temporarily unlock the history protection. (if the password is correct!)
     */
    public void tempUnlock()
    {
        if(this.locked)
        {
            char[] password1 = this.ui.askPassword("Please enter the password:", "Unlock");
            if(new String(password1).equals(new String(this.history.getProtection())))
            {
                this.locked = false;
                this.loadHistoryUI();
            }
            else
            {
                this.getLogger().log(Level.WARNING, "The password is incorrect!");
                this.ui.fireInformationMessage("The password is incorrect!", "Unlock");
            }
        }
        else
        {
            this.getLogger().log(Level.INFO, "Nothing to unlock!");
            this.ui.fireInformationMessage("Nothing to unlock!", "Unlock");
        }
    }

    /**
     * @return Whether the history is locked or not.
     */
    public boolean isLocked()
    {
        return locked;
    }

    /**
     * Adds the current post to the queue.
     */
    public void addToQueue()
    {
        if(this.isCurrentPostGoodToPost())
        {
            Post pNew = this.ui.getCurrentPost();

            Post pOld = this.getQueue().getExactPost(pNew);

            if(pOld == null)
            {
                this.queue.getQueue().add(pNew);
                this.getLogger().log(Level.INFO, "Post \"{0}\" has been queued.", pNew.getDisplayTitle());
            }
            else
            {
                if(this.ui.fireYesNoQuestion("This post has already been queued. Do you want to add it again?", "Queue post"))
                {
                    this.queue.getQueue().add(pNew);
                    this.getLogger().log(Level.INFO, "Post \"{0}\" has been queued.", pNew.getDisplayTitle());
                }
            }

            this.saveQueue();
            this.loadQueueUI();
        }
    }

    /**
     * Saves the queue to disk.
     */
    public void saveQueue()
    {
        PersistenceController.saveQueue(this.queue);
    }

    /**
     * @return The list of queued posts.
     */
    public Queue getQueue()
    {
        return queue;
    }

    /**
     * Removes the selected item in the queue list.
     */
    public void removeSelectedQueueItem()
    {
        String selected = this.ui.getSelectedQueueItem();

        for(Post p : this.queue.getQueue())
        {
            if(p.getDisplayTitle().equals(selected))
            {
                this.queue.getQueue().remove(p);
                break;
            }
        }

        this.saveQueue();
        this.loadQueueUI();
    }

    /**
     * Starts posting the queue to the selected forums.
     */
    public void startBatchPosting()
    {
        String[] selectedForums = this.ui.getSelectedForums();
        String message = "";
        
        if(selectedForums.length < 1)
        {
            message = "Please select one or more forums to post to.";
            this.getLogger().log(Level.WARNING, message);
            this.fireMessage(message, "Batch posting error");
        }
        else
        {
            BatchPosterStarter bp = new BatchPosterStarter();
            bp.start();
        }
    }

    /**
     * Sets the number of simultaneous threads while batch posting.
     * @param value The number of threads.
     */
    public void setBatchPostingThreads(int value)
    {
        this.preferences.setBatchPostThreads(value);
        this.savePreferences();
    }

    /**
     * Sets the cool down period (in seconds) between posting to the same forum.s
     * @param value The value in seconds.
     */
    public void setCoolDownPeriod(int value)
    {
        this.preferences.setCoolDownPeriodInSeconds(value);
        this.savePreferences();
    }

    /**
     * Saves the preferences to disk.
     */
    public void savePreferences()
    {
        PersistenceController.savePreferences(this.preferences);
    }

    /**
     * @return The preferences.
     */
    public Preferences getPreferences()
    {
        return preferences;
    }

    /**
     * @return The cool down queue.
     */
    public CoolDownQueue getCoolDownQueue()
    {
        return coolDownQueue;
    }

    /**
     * Sets the number of attempts.
     * @param value the number.
     */
    public void setRetryAttemps(int value)
    {
        this.preferences.setRetry(value);
        this.savePreferences();
    }

    /**
     * Checks whether the current post is good to post.
     * @return True if the post is correct, false otherwise.
     */
    public boolean isCurrentPostGoodToPost()
    {
        String err = "";
        Post post = this.ui.getCurrentPost();

        if(post.getTopicTitle().length() < 4) //Min 5 long
        {
            err = "Please enter a topic title.";
            DomainController.getInstance().getLogger().log(Level.WARNING, err);
            DomainController.getInstance().fireMessage(err, "Error");
        }
        else
        {
            if(post.getPost().length() < 9) //Min 10 long
            {
                err = "Please enter a post body.";
                DomainController.getInstance().getLogger().log(Level.WARNING, err);
                DomainController.getInstance().fireMessage(err, "Error");
            }
            else
            {
                return true;
            }
        }

        return false;
    }

    /**
     * @return The selected post. 
     */
    public Post getSelectedPostInQueueUI()
    {
        String selected = this.ui.getSelectedQueueItem();

        for(Post p : this.queue.getQueue())
        {
            if(p.getDisplayTitle().equals(selected))
            {
                return p;
            }
        }
        
        return null;
    }

    public void showErrorAsHTML(int errorID)
    {
        if(errorID > 0)
        {
            Error error = this.errorCollection.getErrorByID(errorID);
            if(error != null)
            {
                String theError = error.getError();
                this.ui.showErrorAsHTML(theError == null || theError.equals("") ? "Received empty response" : theError);
            }
        }
    }

    /**
     * Initializes a new empty error list.
     */
    public void clearErrorList()
    {
        this.errorCollection = new ErrorCollection();
    }

    /**
     * @return The error collection.
     */
    public ErrorCollection getErrorCollection()
    {
        return errorCollection;
    }

    /**
     * Notifies the UI to show the posting errors.
     */
    public void showPostingErrors()
    {
        this.ui.showPostingErrors();
    }

    /**
     * Exports the forums to disk.
     * @param file Where to save.
     */
    public void exportForums(File file)
    {
        if(file != null)
            PersistenceController.saveText(file, ForumExporter.forumsToXML());
    }

    /**
     * Imports forums into the program.
     * @param file Location of the XML file.
     */
    public void importForums(File file)
    {
        if(file != null && file.canRead())
            ForumImporter.fromXML(PersistenceController.getText(file));
    }

    /**
     * Sets the loading ui.
     * @param lui The loading ui.
     */
    public void setLoadingUI(LoadingUI lui)
    {
        this.loadingUI = lui;
    }

    /**
     * Checks for updates on a separate thread.
     */
    public void checkForUpdates()
    {
        new UpdateChecker().start();
    }
}